🎬 上一节我们理解了视图是什么,这一节就要 真正动手 了。视图的管理就像普通表一样,离不开这五件事:创建、查看、修改、删除、更新。
本节所有例子都基于学生选课系统的三张表:s(学生)、c(课程)、sc(选课)。请先按下面的"准备工作"在自己的 MySQL 里把环境搭起来,然后跟着每个例子动手敲一遍 —— 视图这个东西,看十遍不如自己写一遍。
📦 准备工作:搭建练习环境
请打开你的 MySQL 客户端(命令行 / Workbench / Navicat 都可以),把下面整段代码复制粘贴 进去执行一次。这会创建一个名为 school 的数据库,里面有 3 张表和测试数据。本节后面所有例子都基于这套数据。
-- ① 创建并使用数据库
CREATE DATABASE IF NOT EXISTS school CHARSET=utf8mb4;
USE school;
-- ② 学生表 s
CREATE TABLE s (
sno VARCHAR(10) PRIMARY KEY, -- 学号
sn VARCHAR(20) NOT NULL, -- 姓名
sex CHAR(2), -- 性别
age INT, -- 年龄
maj VARCHAR(20), -- 专业
dept VARCHAR(20) -- 学院
);
-- ③ 课程表 c
CREATE TABLE c (
cno VARCHAR(10) PRIMARY KEY, -- 课程号
cn VARCHAR(40) NOT NULL, -- 课程名
ct INT -- 学时
);
-- ④ 选课表 sc
CREATE TABLE sc (
sno VARCHAR(10),
cno VARCHAR(10),
score INT,
PRIMARY KEY(sno, cno)
);
-- ⑤ 插入测试数据
INSERT INTO s VALUES
('s01','张三','男',20,'计算机','信息学院'),
('s02','李四','女',19,'软件工程','信息学院'),
('s03','王五','男',21,'物理','理学院'),
('s04','赵六','女',20,'数学','理学院'),
('s05','钱七','男',22,'计算机','信息学院');
INSERT INTO c VALUES
('c01','数据库原理',64),
('c02','操作系统',48),
('c03','高等数学',80);
INSERT INTO sc VALUES
('s01','c01',85),
('s01','c02',76),
('s02','c01',92),
('s03','c03',88),
('s04','c03',79);
-- ⑥ 验证一下
SELECT * FROM s;
| sno | sn | sex | age | maj | dept |
|---|---|---|---|---|---|
| s01 | 张三 | 男 | 20 | 计算机 | 信息学院 |
| s02 | 李四 | 女 | 19 | 软件工程 | 信息学院 |
| s03 | 王五 | 男 | 21 | 物理 | 理学院 |
| s04 | 赵六 | 女 | 20 | 数学 | 理学院 |
| s05 | 钱七 | 男 | 22 | 计算机 | 信息学院 |
看到 5 行学生数据就说明环境搭好了。如果某一步报错,最常见的原因是 没有先 USE school 或者 表名已存在(之前敲过一次了)—— 此时可以先 DROP DATABASE school; 然后重来。
① 创建视图:CREATE VIEW
完整语法
CREATE [OR REPLACE] [ALGORITHM={UNDEFINED | MERGE | TEMPTABLE}]
[DEFINER = {user | CURRENT_USER}]
VIEW 视图名 [(视图字段列表)]
AS 查询语句
[WITH [CASCADED | LOCAL] CHECK OPTION];
方括号 [] 里的都是可选的。把可选项全删掉,最简形式就这一行:
CREATE VIEW 视图名 AS 查询语句;
翻译过来就是:"创建一个叫 XX 的视图,它的内容就是后面这条 SELECT 查出来的东西。"
语法关键字详解
| 关键字 | 作用说明 |
|---|---|
| OR REPLACE | 如果数据库中已有同名视图,直接替换,否则报错。 |
| ALGORITHM | 视图的执行算法。UNDEFINED(默认)让 MySQL 自己决定;MERGE 把视图查询和外层查询合并执行(效率高);TEMPTABLE 把视图结果先存进临时表(无法做更新)。 |
| DEFINER | 指定视图的"创建者/所有者",影响权限检查。多数场景用默认值即可。 |
| 视图字段列表 | 给视图的列起新名字(如 (sno, sname))。省略时,沿用查询语句中字段的原名。 |
| AS 查询语句 | 视图的核心 —— 一条完整的 SELECT 语句,决定了视图能查出什么数据。 |
| WITH CHECK OPTION | 当通过视图执行 INSERT/UPDATE 时,只允许满足视图 WHERE 条件 的数据通过;不满足的会被拒绝。 |
三种典型创建方式
需求:创建"信息学院学生"视图 s_view
把"WHERE dept='信息学院'"封装起来,以后查信息学院的学生只需 SELECT * FROM s_view。
CREATE VIEW s_view
AS SELECT * FROM s
WHERE dept = '信息学院';
-- 创建后立刻验证:像查表一样查视图
SELECT * FROM s_view;
| sno | sn | sex | age | maj | dept |
|---|---|---|---|---|---|
| s01 | 张三 | 男 | 20 | 计算机 | 信息学院 |
| s02 | 李四 | 女 | 19 | 软件工程 | 信息学院 |
| s05 | 钱七 | 男 | 22 | 计算机 | 信息学院 |
AS SELECT * 中的 AS 是 "作为" 的意思 —— "把这个视图作为这个 SELECT 查询的结果"。它不是别名,是引出视图定义的关键字。
需求:创建"学生选课情况"视图 s_sc_c_view
把学生姓名(s 表)+ 课程名(c 表)+ 成绩(sc 表)拼起来,封装成一个视图,以后看选课情况一句话搞定。
CREATE VIEW s_sc_c_view(sno, sname, cname, score)
AS SELECT s.sno, s.sn, c.cn, sc.score
FROM s, c, sc
WHERE s.sno = sc.sno
AND sc.cno = c.cno;
SELECT * FROM s_sc_c_view;
| sno | sname | cname | score |
|---|---|---|---|
| s01 | 张三 | 数据库原理 | 85 |
| s01 | 张三 | 操作系统 | 76 |
| s02 | 李四 | 数据库原理 | 92 |
| s03 | 王五 | 高等数学 | 88 |
| s04 | 赵六 | 高等数学 | 79 |
视图名后面的 (sno, sname, cname, score) 是 视图字段列表。
原表中字段叫 sn(不直观),但通过视图字段列表,我们对外展示的列名是更易读的 sname。位置一一对应 —— 第 1 个字段名对应 SELECT 中第 1 列,以此类推。
如果省略这个列表,视图列名就用 SELECT 后面的原字段名 sno, sn, cn, score。
例子里用的是 逗号分隔多表 + WHERE 写连接条件 的旧写法(教材风格)。在实际项目中更推荐用 JOIN ... ON 显式连接,可读性更好:
FROM s JOIN sc ON s.sno = sc.sno JOIN c ON sc.cno = c.cno
两种写法效果一样,但 JOIN 写法把"连接"和"过滤"分得更清楚。
需求:在 s_view 基础上,再筛出"计算机专业"的学生
视图本身就可以当成一张"表"来用,所以也可以基于已有视图再创建视图 —— 这就是"视图的视图"。
-- 前提:已有 s_view(信息学院学生)
CREATE VIEW s_maj_view
AS SELECT * FROM s_view
WHERE maj = '计算机';
SELECT * FROM s_maj_view;
| sno | sn | sex | age | maj | dept |
|---|---|---|---|---|---|
| s01 | 张三 | 男 | 20 | 计算机 | 信息学院 |
| s05 | 钱七 | 男 | 22 | 计算机 | 信息学院 |
这相当于给筛选条件叠加了两层:第一层(s_view)筛"信息学院",第二层(s_maj_view)筛"计算机专业"。这种 分层封装 的思路在工程上很有用 —— 把复杂的筛选拆成多个简单视图,每一层都好理解、好维护。
创建视图的注意事项
- SELECT 中 不能包含系统变量、用户变量,也不能包含处理语句的参数。
- FROM 子句中 不能包含子查询(即不能 FROM (SELECT...))。
- 视图依赖的基本表如果被删除,视图就废了 —— 用
CHECK TABLE 表名检查表的状态。 - 不能为 临时表(TEMPORARY TABLE)创建视图。
- ALGORITHM 参数会影响部分查询结果(特别是 TEMPTABLE 模式下,视图变成只读)。
📌 补充:用 MySQL Workbench 图形化创建视图
步骤:
- 在 Navigator 窗格的 Schemas 选项卡下,找到目标数据库;
- 右键点击该数据库下的 Views 节点,选择 Create View...;
- 在右侧打开的 DDL 文本框里输入完整的
CREATE VIEW ...语句; - Workbench 会根据你的 SQL 自动调整 Name 文本框的内容;
- 检查无误后,点击 Apply 按钮提交。
本质上,Workbench 也只是把你的 SQL 发给 MySQL 服务器执行 —— 所以学好 SQL 永远是第一位的。
② 查看视图的定义
创建完视图,怎么知道它长什么样?MySQL 提供了两条命令:
方式 1:DESCRIBE 视图名 —— 看"结构"
DESCRIBE s_view;
-- 也可以缩写为 DESC s_view;
| Field | Type | Null | Key | Default |
|---|---|---|---|---|
| sno | varchar(10) | NO | NULL | |
| sn | varchar(20) | NO | NULL | |
| sex | char(2) | YES | NULL | |
| age | int | YES | NULL | |
| maj | varchar(20) | YES | NULL | |
| dept | varchar(20) | YES | NULL |
DESCRIBE 输出的是"字段层面"的结构信息:每一列叫什么、是什么类型、能不能为空。它把视图当成一张普通表来描述 —— 因为对使用者来说,视图就是表。
方式 2:SHOW CREATE VIEW —— 看"原始定义"
SHOW CREATE VIEW s_view;
| View | Create View(节选) |
|---|---|
| s_view | CREATE ALGORITHM=UNDEFINED ... VIEW `s_view` AS SELECT ... FROM `s` WHERE `dept`='信息学院' |
DESCRIBE:看视图"有哪些字段" → 关心使用层面。
SHOW CREATE VIEW:看视图"是怎么定义的" → 关心实现层面,能看到完整 SQL。
③ 修改视图的定义
视图建好后想改?有两种办法:ALTER VIEW 和 CREATE OR REPLACE VIEW。
方式 1:ALTER VIEW(推荐)
ALTER [ALGORITHM={UNDEFINED | MERGE | TEMPTABLE}]
[DEFINER = {user | CURRENT_USER}]
VIEW 视图名 [(视图字段列表)]
AS 查询语句
[WITH [CASCADED | LOCAL] CHECK OPTION];
例子:把 s_view 改成显示"理学院"的学生,并把字段重命名
ALTER VIEW s_view(s_id, s_name, s_maj)
AS SELECT sno, sn, maj
FROM s
WHERE dept = '理学院';
SELECT * FROM s_view;
| s_id | s_name | s_maj |
|---|---|---|
| s03 | 王五 | 物理 |
| s04 | 赵六 | 数学 |
方式 2:CREATE OR REPLACE VIEW
用上一节学过的 OR REPLACE:写一遍 CREATE VIEW,加上 OR REPLACE 关键字,有同名视图就替换,没有就新建。
CREATE OR REPLACE VIEW s_view
AS SELECT sno, sn, age
FROM s
WHERE dept = '信息学院';
务必 仔细核对视图名! 如果你 把名字打错了(例如想改 s_view 却写成了 s_veiw),它会:
① 找不到 s_veiw,所以不会"替换";
② 然后按你给的"错误名字"新建一个视图。
结果是:原来的 s_view 没动,多了个错误名字的 s_veiw。ALTER VIEW 没有这个风险 —— 名字不存在直接报错,更安全。
④ 删除视图:DROP VIEW
语法
DROP VIEW [IF EXISTS] 视图名1 [, 视图名2, ...] [RESTRICT | CASCADED];
IF EXISTS:如果视图存在才删除。强烈建议加上,因为删除一个不存在的视图会报错,加上这个就只是给个警告,脚本不中断。
视图名后面可以 用逗号写多个,一次删除多个视图。
例子:删除视图
-- 删除单个视图
DROP VIEW s_view;
-- 安全删除(不存在也不报错)
DROP VIEW IF EXISTS s_view;
-- 一次删除多个
DROP VIEW IF EXISTS s_view, s_maj_view, s_sc_c_view;
删除是不可逆操作。在生产环境删视图前,先用 SHOW CREATE VIEW 视图名 把定义打印出来保存好 —— 万一删错了还能照着原样重建。
⑤ 通过视图更新数据
视图本身不存数据,所以 对视图的 INSERT/UPDATE/DELETE 操作,最终会被转换为对基本表的相应操作。
你以为你在改视图,其实改的是视图背后的基本表。视图只是"中转站"。所以视图改完,去查基本表,会发现基本表的数据真的变了。
建立一个可更新的视图
-- 这个视图等于把整张 s 表搬过来,最简单的"可更新视图"
CREATE VIEW s_update_view
AS SELECT * FROM s;
三种 DML 操作演示
通过视图插入一条新学生
INSERT INTO s_update_view
VALUES('s10', '韩义', '男', 19, '计算机', '信息学院');
-- 验证:去基本表 s 中查 s10
SELECT * FROM s WHERE sno = 's10';
| sno | sn | sex | age | maj | dept |
|---|---|---|---|---|---|
| s10 | 韩义 | 男 | 19 | 计算机 | 信息学院 |
通过视图修改 s10 的年龄
UPDATE s_update_view
SET age = 20
WHERE sno = 's10';
SELECT sno, sn, age FROM s WHERE sno = 's10';
| sno | sn | age |
|---|---|---|
| s10 | 韩义 | 20 |
通过视图删除 s10
DELETE FROM s_update_view
WHERE sno = 's10';
SELECT * FROM s WHERE sno = 's10';
Empty set (0 行)—— s10 已经从基本表 s 中真正删除了
哪些视图 不能 被更新?
视图本质是"虚拟"的,有些情况下 MySQL 没办法把"对视图的修改"翻译回"对基本表的修改",这时视图就 变成只读的。下列任意一种情况,视图都不可更新:
- 视图字段或查询语句中包含 聚合函数(SUM、COUNT、AVG、MAX、MIN);
- 视图字段是通过 表达式或计算 得到的(如
price * 0.8 AS discount); - 视图定义中包含
DISTINCT、GROUP BY、ORDER BY、HAVING等子句; - 查询语句中使用了集合运算
UNION或UNION ALL; - 视图的列来自子查询的列;
- 创建视图时指定了
ALGORITHM = TEMPTABLE(MySQL 把视图结果先存进临时表,无法反向更新); - 视图依赖的"上游视图"本身就是不可更新的。
能让"视图的一行"明确对应到"基本表的一行" 的视图才能更新。一旦做了聚合、去重、计算、合并 —— 一行视图数据可能由多行基本表数据"加工"出来 —— MySQL 就没法反推回去,视图就只能读不能写。
即便视图本身满足上述限制都不触犯,多张基本表 JOIN 出来的视图,更新也常常受限:单条 INSERT 可能要往两张表里写数据,MySQL 不会自动帮你拆。所以工程实践中:用于更新的视图通常基于单表。 多表视图主要用于查询。
📋 本节小结
- 创建:
CREATE [OR REPLACE] VIEW 名 AS SELECT...—— 单表/多表/视图层叠都支持。 - 查看:
DESCRIBE 名看结构;SHOW CREATE VIEW 名看完整定义。 - 修改:
ALTER VIEW 名 AS SELECT...(推荐);或CREATE OR REPLACE VIEW(注意名字别打错)。 - 删除:
DROP VIEW [IF EXISTS] 名1, 名2, ...。 - 更新数据:INSERT/UPDATE/DELETE 都可,本质是改基本表;但聚合、计算、DISTINCT、GROUP BY 等会让视图变只读。
- 看到一段需求("查 XX 学院的学生"),能立刻写出对应的 CREATE VIEW;
- 给定一个视图,知道它能不能被 INSERT/UPDATE/DELETE;
- 写脚本删视图前,会主动加上
IF EXISTS。
✏️ 综合练习
以下题目都基于本节的 s、c、sc 三张表。建议先在自己脑子里写答案,再点选项验证。
下列创建视图的语句中,语法错误 的是?
CREATE VIEW v AS SELECT * FROM s;CREATE OR REPLACE VIEW v AS SELECT sno FROM s;CREATE VIEW v SELECT * FROM s;CREATE VIEW v(id, name) AS SELECT sno, sn FROM s;✅ 正确答案:C
C 选项 缺少 AS 关键字。AS 是连接"视图名"和"查询语句"的必需关键字,不能省略。其他三项语法都正确:A 是最简形式;B 用了 OR REPLACE;D 用了视图字段列表。
执行 CREATE VIEW v(a, b, c) AS SELECT sno, sn, dept FROM s; 后,下列哪条语句能正确查出数据?
SELECT sno, sn, dept FROM v;SELECT a, b, c FROM v;SELECT * FROM v WHERE sno = 's01';✅ 正确答案:B
视图字段列表 (a, b, c) 给视图的列重命名了 —— 对外只能看到 a、b、c 这三个列名。原表的 sno、sn、dept 在视图层面已经"不存在"了,所以 A 和 C 都会报错。SELECT * 没问题,但 WHERE sno = ... 中的 sno 在视图里已经叫 a 了,所以 C 错。
想把已有视图 s_view 改成查询"理学院"的学生,最 安全 的语句是?
CREATE VIEW s_view AS SELECT * FROM s WHERE dept='理学院';ALTER VIEW s_view AS SELECT * FROM s WHERE dept='理学院';UPDATE s_view SET dept='理学院';CREATE OR REPLACE VIEW s_veiw AS SELECT * FROM s WHERE dept='理学院';✅ 正确答案:B
A 错,s_view 已存在,CREATE 会报错;C 错,UPDATE 是改数据不是改视图定义;D 错,名字打错了(s_veiw),结果会新建一个错的视图。ALTER VIEW 是最安全的:名字不存在直接报错提醒你,名字正确就稳稳替换定义。
下列哪个视图 不能 通过 INSERT/UPDATE/DELETE 更新数据?
CREATE VIEW v AS SELECT * FROM s;CREATE VIEW v AS SELECT sno, sn FROM s WHERE age > 18;CREATE VIEW v AS SELECT dept, COUNT(*) FROM s GROUP BY dept;CREATE VIEW v AS SELECT sno, age FROM s ORDER BY sno; (注意:MySQL 中 ORDER BY 在视图里不会真的影响可更新性,但下面的 GROUP BY 会)✅ 正确答案:C
C 同时使用了 聚合函数 COUNT(*) 和 GROUP BY 子句,违反了视图更新的两条限制。视图里"信息学院 3 个人"这一行是基本表里 3 行数据汇总出来的,MySQL 没法把"修改这一行"翻译回"修改基本表的哪 3 行",所以视图只读。A、B 是简单的单表选取,可更新;D 仅有 ORDER BY,不影响数据的"行对应"关系。
想要创建一个名为 v_score 的视图,显示每个学生的"姓名 + 课程名 + 成绩",并允许在视图已存在时直接替换。下列 SQL 中 正确且最佳 的是?
CREATE VIEW v_score AS SELECT s.sn, c.cn, sc.score FROM s, c, sc;CREATE OR REPLACE VIEW v_score AS SELECT s.sn, c.cn, sc.score FROM s, c, sc WHERE s.sno=sc.sno AND sc.cno=c.cno;ALTER VIEW v_score AS SELECT s.sn, c.cn, sc.score FROM s, sc, c;CREATE OR REPLACE VIEW v_score AS SELECT * FROM s, c, sc;✅ 正确答案:B
A 错:少了表连接条件(缺 WHERE),会产生笛卡尔积导致结果爆炸;C 错:ALTER 在视图不存在时会报错,不满足"已存在则替换、不存在则新建"的需求;D 错:少了 JOIN 条件且 SELECT * 会返回无意义的所有字段(包含重复列)。B 同时满足"OR REPLACE"和"正确的连接条件",最佳。
🛠️ 实操任务(课后练习)
任务 1 · 基础
创建一个视图 v_male_stu,显示所有 男生 的学号、姓名、年龄。
查看参考答案
CREATE VIEW v_male_stu
AS SELECT sno, sn, age FROM s WHERE sex = '男';
任务 2 · 中等
创建一个视图 v_pass,显示 所有及格(成绩 ≥ 60) 的选课记录,包含字段:学生姓名、课程名、成绩。然后通过这个视图查询"数据库原理"课的所有及格学生。
查看参考答案
-- 创建视图
CREATE VIEW v_pass
AS SELECT s.sn, c.cn, sc.score
FROM s, c, sc
WHERE s.sno = sc.sno
AND sc.cno = c.cno
AND sc.score >= 60;
-- 通过视图查询
SELECT * FROM v_pass WHERE cn = '数据库原理';
任务 3 · 进阶(思考题)
有视图 v_avg_score 定义为 SELECT sno, AVG(score) FROM sc GROUP BY sno。判断:能否 通过这个视图执行 UPDATE v_avg_score SET ...?为什么?
查看参考答案
不能。原因是这个视图同时违反了两条限制:① 包含聚合函数 AVG();② 包含 GROUP BY 子句。
视图里的"一行"代表"某个学生的平均分",是由基本表 sc 中该学生的多行成绩聚合出来的。如果你想 UPDATE 平均分,MySQL 完全没法翻译回"修改基本表的哪几行" —— 是要全部成绩同时改?还是改一行?指令本身是不明确的,所以 MySQL 直接禁止。